<!DOCTYPE html>
<html>

<head>
    <title>Customize your own components</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.6/build/dat.gui.min.js"></script>
    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maptalks/dist/maptalks.css">
    <script type="text/javascript" src="./js/maptalks.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/three@0.97.0/build/three.min.js"></script>
    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/npm/three@0.97.0/examples/js/libs/stats.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/lz-string@1.4.4/libs/lz-string.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/maptalks.three/dist/maptalks.three.js"></script>
    <script type="text/javascript" src="js/geoutil.js"></script>
    <script type="text/javascript" src="buildings.js"></script>
    <style>
        html,
        body {
            margin: 0px;
            height: 100%;
            width: 100%;
        }

        #map {
            width: 100%;
            height: 100%;
            background-color: #000;
        }
    </style>
</head>

<body>
    <div id="map"></div>
    <script>

        var map = new maptalks.Map("map", {
            center: [13.416935229170008, 52.529564137540376],
            zoom: 15,
            pitch: 70,
            bearing: 180,
            centerCross: true,
            doubleClickZoom: false,
            // baseLayer: new maptalks.TileLayer('tile', {
            //     urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
            //     subdomains: ['a', 'b', 'c', 'd'],
            //     attribution: '&copy; <a href="http://osm.org">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/">CARTO</a>'
            // })
        });
        // features to draw
        var features = [];
        buildings.forEach(function (b) {
            features = features.concat(b.features);
        });
        // the ThreeLayer to draw buildings
        var threeLayer = new maptalks.ThreeLayer('t', {
            forceRenderOnMoving: true,
            forceRenderOnRotating: true,
            // animation: true
        });
        var stats;
        threeLayer.prepareToDraw = function (gl, scene, camera) {
            stats = new Stats();
            stats.domElement.style.zIndex = 100;
            document.getElementById('map').appendChild(stats.domElement);

            var light = new THREE.DirectionalLight(0xffffff);
            light.position.set(0, -10, 10).normalize();
            scene.add(light);
            var material = new THREE.MeshBasicMaterial({ color: '#3e35cf' });
            var meshs = [];
            var heightPerLevel = 10;
            var polygons = features.map(f => {
                const polygon = maptalks.GeoJSON.toGeometry(f);
                var levels = f.properties.levels || 1;
                polygon.setProperties({
                    height: heightPerLevel * levels,
                });
                return polygon;
            });
            const extrudePolygons = threeLayer.toExtrudePolygons(polygons, { topColor: '#fff', interactive: false }, material);
            meshs.push(extrudePolygons);
            threeLayer.addMesh(meshs);
            addLines();
        };
        threeLayer.addTo(map);


        var material = new THREE.LineBasicMaterial({
            linewidth: 1,
            color: 0x00ffff,
            opacity: 0.8,
            transparent: true
        });

        var lineMaterial = new THREE.LineBasicMaterial({
            linewidth: 1,
            color: 0xd3887,
            opacity: 0.2,
            transparent: true
        });
        var lineTrips;
        function addLines() {
            fetch('./data/berlin-roads.txt').then(function (res) {
                return res.text();
            }).then(function (geojson) {
                geojson = LZString.decompressFromBase64(geojson);
                geojson = JSON.parse(geojson);
                var lineStrings = maptalks.GeoJSON.toGeometry(geojson);
                var timer = 'generate line time';
                console.time(timer);
                var list = [];
                lineStrings.forEach(lineString => {
                    list.push({
                        lineString,
                        len: lineLength(lineString)
                    });
                });
                list = list.sort(function (a, b) {
                    return b.len - a.len
                });

                var offset = 500;
                lineTrips = list.slice(0, offset).map(d => {
                    var line = new LineTrip(d.lineString, {
                        chunkLength: d.len / 100,
                        speed: 1,
                        altitude: 2,
                    }, material, threeLayer)
                    return line;
                });
                var line = threeLayer.toLines(list.map(l => {
                    return l.lineString
                }), { interactive: false }, lineMaterial);

                console.log('lines.length:', lineTrips.length);
                console.timeEnd(timer);
                threeLayer.addMesh(line);
                threeLayer.addMesh(lineTrips);
                initGui();
                animation();
            })
        }

        function animation() {
            // layer animation support Skipping frames
            threeLayer._needsUpdate = !threeLayer._needsUpdate;
            if (threeLayer._needsUpdate) {
                threeLayer._renderer.clearCanvas();
                threeLayer.renderScene();
            }
            stats.update();
            requestAnimationFrame(animation);
        }


        function initGui() {
            var params = {
                add: true,
                color: material.color.getStyle(),
                show: true,
                opacity: 1,
                altitude: 0
            };
            var gui = new dat.GUI();
            gui.add(params, 'add').onChange(function () {
                if (params.add) {
                    threeLayer.addMesh(lineTrips);
                } else {
                    threeLayer.removeMesh(lineTrips);
                }
            });
            gui.addColor(params, 'color').name('line color').onChange(function () {
                material.color.set(params.color);
                lineTrips.forEach(function (mesh) {
                    mesh.setSymbol(material);
                });
            });
            gui.add(params, 'opacity', 0, 1).onChange(function () {
                material.opacity = params.opacity;
                lineTrips.forEach(function (mesh) {
                    mesh.setSymbol(material);
                });
            });
            // gui.add(params, 'show').onChange(function () {
            //     lineTrips.forEach(function (mesh) {
            //         if (params.show) {
            //             mesh.show();
            //         } else {
            //             mesh.hide();
            //         }
            //     });
            // });
            gui.add(params, 'altitude', 0, 300).onChange(function () {
                lineTrips.forEach(function (mesh) {
                    mesh.setAltitude(params.altitude);
                });
            });
        }



        //default values
        var OPTIONS = {
            chunkLength: 50,
            speed: 1,
            altitude: 0,
            interactive: false
        };

        /**
         * custom component
         * */

        class LineTrip extends maptalks.BaseObject {
            constructor(lineString, options, material, layer) {
                options = maptalks.Util.extend({}, OPTIONS, options, { layer, lineString });
                super();
                //Initialize internal configuration
                // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
                this._initOptions(options);

                const { altitude, chunkLength, speed } = options;
                const chunkLines = lineSlice(lineString, chunkLength);

                const centerPt = layer.coordinateToVector3(lineString.getCenter());
                //cache position for  faster computing,reduce double counting
                const positionMap = {};
                for (let i = 0, len = chunkLines.length; i < len; i++) {
                    const chunkLine = chunkLines[i];
                    for (let j = 0, len1 = chunkLine.length; j < len1; j++) {
                        const lnglat = chunkLine[j];
                        const key = lnglat.join(',').toString();
                        if (!positionMap[key]) {
                            positionMap[key] = layer.coordinateToVector3(lnglat).sub(centerPt);
                        }
                    }
                }

                let len = 0;
                chunkLines.forEach(element => {
                    len += element.length;
                });
                len *= 3;
                //generate geometry
                const result = getChunkLinesPosition(chunkLines.slice(0, 1), layer, positionMap, centerPt);
                const positions = result.positions;
                const geometry = new THREE.BufferGeometry();
                const ps = new Float32Array(len); // 3 vertices per point
                geometry.addAttribute('position', new THREE.BufferAttribute(ps, 3).setDynamic(true));
                setLineGeometryAttribute(geometry, positions.slice(0, 6));

                this._createLine(geometry, material);

                //set object3d position
                const z = layer.distanceToVector3(altitude, altitude).x;
                const center = lineString.getCenter();
                const v = layer.coordinateToVector3(center, z);
                this.getObject3d().position.copy(v);

                this._params = {
                    index: 0,
                    len: chunkLines.length,
                    chunkLines,
                    layer,
                    speed: Math.min(1, speed),
                    idx: 0,
                    positions: [],
                    positionMap,
                    centerPt
                };
                this._init();
            }

            _init() {
                const { len, chunkLines, layer, positionMap, centerPt } = this._params;
                for (let i = 0; i < len; i++) {
                    let ps = [];
                    if (i > 0) {
                        const prePs = this._params.positions[i - 1];
                        for (let j = 0, len1 = prePs.length; j < len1; j++) {
                            ps.push(prePs[j]);
                        }
                        const nextPs = getChunkLinesPosition([chunkLines[i]], layer, positionMap, centerPt).positions;
                        for (let j = 0, len1 = nextPs.length; j < len1; j++) {
                            ps.push(nextPs[j]);
                        }
                    } else {
                        const result = chunkLines.slice(0, i);
                        ps = getChunkLinesPosition(result, layer, positionMap, centerPt).positions;
                    }
                    this._params.positions[i] = ps;
                }
            }


            _animation() {
                const { index, positions, idx, speed, len, chunkLines, layer, positionMap, centerPt } = this._params;
                const i = Math.round(index);
                if (i > idx) {
                    this._params.idx++;
                    let ps = positions[i];
                    if (!ps) {
                        const result = chunkLines.slice(0, i);
                        ps = getChunkLinesPosition(result, layer, positionMap, centerPt).positions;
                        this._params.positions[i] = ps;
                    }
                    setLineGeometryAttribute(this.getObject3d().geometry, ps);
                    this.getObject3d().geometry.attributes.position.needsUpdate = true;
                }
                if (index >= len) {
                    this._params.index = -1;
                    this._params.idx = -1;
                }
                this._params.index += speed;
            }
        }
    </script>
</body>

</html>